/*
 * ScrollBar.java  0.4.0 / Oct 26, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing;

import com.cute4j.swing.events.ChangeEvent;
import com.cute4j.swing.events.ChangeListener;
import com.cute4j.swing.events.SwingEvent;
import com.cute4j.swing.properties.ScrollBarProperties;

/**
 * ScrollBar Component.
 *
 * @cute4j.native
 *    generate="header"
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public class ScrollBar extends Component
                       implements SwingConstants, ScrollBarProperties {

// Package-Private Static Fields. ----------------------------------------------

  static final int DEFAULT_MINIMUM = 0;

  static final int DEFAULT_MAXIMUM = 100;

  static final int DEFAULT_VALUE = DEFAULT_MINIMUM;

  static final int DEFAULT_ORIENTATION = VERTICAL;

// Static Initialization. ------------------------------------------------------

  static {
    _initialize();
  }

// Private Fields. -------------------------------------------------------------

  private BoundedRangeModel model;
  private boolean blockModelEvents;
  private int minimum;
  private int maximum;
  private int value;
  private int unitIncrement;
  private int blockIncrement;
  private int orientation;
  private ChangeEvent changeEvent; // NOPMD
  private final ChangeListener changeListener;

// Public Constructors. --------------------------------------------------------

  /**
   * Constructs new ScrollBar object.
   */
  public ScrollBar() {
    this(DEFAULT_ORIENTATION, DEFAULT_MINIMUM, DEFAULT_MAXIMUM, DEFAULT_VALUE);
  }

  /**
   * Constructs new ScrollBar object.
   *
   * @param orientation the orientation of the scrollbar
   */
  public ScrollBar(int orientation) {
    this(orientation, DEFAULT_MINIMUM, DEFAULT_MAXIMUM, DEFAULT_VALUE);
  }

  /**
   * Constructs new ScrollBar object.
   *
   * @param minimum the minimum value of the scrollbar
   * @param maximum the maximum value of the scrollbar
   */
  public ScrollBar(int minimum, int maximum) {
    this(DEFAULT_ORIENTATION, minimum, maximum, (minimum + maximum) / 2);
  }

  /**
   * Constructs new ScrollBar object.
   *
   * @param minimum the minimum value of the scrollbar
   * @param maximum the maximum value of the scrollbar
   * @param value the current value of the scrollbar
   */
  public ScrollBar(int minimum, int maximum, int value) {
    this(DEFAULT_ORIENTATION, minimum, maximum, value);
  }

  /**
   * Constructs new ScrollBar object.
   *
   * @param orientation the orientation of the scrollbar
   * @param minimum the minimum value of the scrollbar
   * @param maximum the maximum value of the scrollbar
   * @param value the current value of the scrollbar
   */
  public ScrollBar(int orientation, int minimum, int maximum, int value) {
    if ((orientation < HORIZONTAL) || (orientation > VERTICAL)) {
      throw new IllegalArgumentException("Invalid orientation");
    }
    if (orientation != DEFAULT_ORIENTATION) {
      _setOrientation(_delegate, orientation); // NOPMD
    }
    this.orientation = orientation;
    changeListener = new ModelListener(this);
    this.minimum = minimum;
    this.maximum = maximum;
    this.value = value;
    enableEvents(SwingEvent.CONTEXT_MENU_EVENT_MASK);
    model = new DefaultBoundedRangeModel(value, 0, minimum, maximum);
    model.addChangeListener(changeListener);
  }

  /**
   * Constructs new ScrollBar object.
   *
   * @param model the model
   */
  public ScrollBar(BoundedRangeModel model) {
    enableEvents(SwingEvent.CONTEXT_MENU_EVENT_MASK);
    orientation = DEFAULT_ORIENTATION;
    changeListener = new ModelListener(this);
    if (model == null) {
      this.model = DefaultBoundedRangeModel.EmptyModel.INSTANCE;
      minimum = DEFAULT_MINIMUM;
      maximum = DEFAULT_MAXIMUM;
      value = DEFAULT_VALUE;
    } else {
      this.model = model;
      minimum = model.getMinimum();
      maximum = model.getMaximum();
      value = model.getValue();
      _setModel(_delegate, model.getMinimum(), model.getMaximum(),
                model.getValue());
      model.addChangeListener(changeListener);
    }
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Returns data model of the scrollbar.
   *
   * @return data model of the scrollbar
   */
  public BoundedRangeModel getModel() {
    return model;
  }

  /**
   * Sets data model of the scrollbar.
   *
   * @param model the data model
   */
  public void setModel(BoundedRangeModel model) { // NOPMD
    final BoundedRangeModel oldModel = this.model;
    if (oldModel != model) { // NOPMD
      if (model == null) {
        if (oldModel == DefaultBoundedRangeModel.EmptyModel.INSTANCE) { // NOPMD
          return;
        }
        model = DefaultBoundedRangeModel.EmptyModel.INSTANCE; // NOPMD
      }
      disposedCheck();
      if (oldModel != null) {
        oldModel.removeChangeListener(changeListener);
      }
      _setModel(_delegate, model.getMinimum(), model.getMaximum(),
                model.getValue());
      this.model = model;
      this.minimum = model.getMinimum();
      this.maximum = model.getMaximum();
      this.value = model.getValue();
      model.addChangeListener(changeListener);
      firePropertyChange(PROPERTY_MODEL, oldModel, model);
    }
  }

  /**
   * Returns current value of the scrollbar.
   *
   * @return current value of the scrollbar
   */
  public int getValue() {
    return model.getValue();
  }

  /**
   * Sets current value of the scrollbar.
   *
   * @param value the current value of the scrollbar
   */
  public void setValue(int value) {
    model.setValue(value);
  }

  /**
   * Returns minimum scrollbar value.
   *
   * @return minimum scrollbar value
   */
  public int getMinimum() {
    return model.getMinimum();
  }

  /**
   * Sets minimum scrollbar value.
   *
   * @param minimum the maximum scrollbar value
   */
  public void setMinimum(int minimum) {
    model.setMinimum(minimum);
  }

  /**
   * Returns maximum scrollbar value.
   *
   * @return maximum scrollbar value
   */
  public int getMaximum() {
    return model.getMaximum();
  }

  /**
   * Sets maximum scrollbar value.
   *
   * @param maximum the maximum scrollbar value
   */
  public void setMaximum(int maximum) {
    model.setMaximum(maximum);
  }

  /**
   * Returns <tt>true</tt> if the scrollbar knob is being dragged.
   *
   * @return <tt>true</tt> if the scrollbar knob is being dragged
   */
  public boolean getValueIsAdjusting() {
    return model.getValueIsAdjusting();
  }

  /**
   * Sets the value of the <tt>valueIsAdjusting</tt> property.
   *
   * @param adjusting the value of the <tt>valueIsAdjusting</tt> property
   */
  public void setValueIsAdjusting(boolean adjusting) {
    model.setValueIsAdjusting(adjusting);
  }

  /**
   * Returns orientation of the scrollbar.
   *
   * @return orientation of the scrollbar
   */
  public int getOrientation() {
    return orientation;
  }

  /**
   * Sets orientation.
   *
   * @param orientation the orientation
   */
  public void setOrientation(int orientation) {
    if ((orientation < HORIZONTAL) || (orientation > VERTICAL)) {
      throw new IllegalArgumentException("Invalid orientation");
    }
    final int oldOrientation = this.orientation;
    if (oldOrientation != orientation) {
      disposedCheck();
      _setOrientation(_delegate, orientation);
      this.orientation = orientation;
      firePropertyChange(PROPERTY_ORIENTATION, oldOrientation, orientation);
    }
  }

  /**
   * Returns unit increment of the scrollbar.
   *
   * @return unit increment of the scrollbar
   */
  public int getUnitIncrement() {
    return unitIncrement;
  }

  /**
   * Sets unit increment.
   *
   * @param unitIncrement the unit increment
   */
  public void setUnitIncrement(int unitIncrement) {
    final int oldUnitIncrement = this.unitIncrement;
    if (oldUnitIncrement != unitIncrement) {
      disposedCheck();
      _setUnitIncrement(_delegate, unitIncrement);
      this.unitIncrement = unitIncrement;
      firePropertyChange(PROPERTY_UNIT_INCREMENT, oldUnitIncrement,
                         unitIncrement);
    }
  }

  /**
   * Returns block increment of the scrollbar.
   *
   * @return block increment of the scrollbar
   */
  public int getBlockIncrement() {
    return blockIncrement;
  }

  /**
   * Sets block increment.
   *
   * @param blockIncrement the block increment
   */
  public void setBlockIncrement(int blockIncrement) {
    final int oldBlockIncrement = this.blockIncrement;
    if (oldBlockIncrement != blockIncrement) {
      disposedCheck();
      _setBlockIncrement(_delegate, blockIncrement);
      this.blockIncrement = blockIncrement;
      firePropertyChange(PROPERTY_BLOCK_INCREMENT, oldBlockIncrement,
                         blockIncrement);
    }
  }

  /**
   * Adds change listener.
   *
   * @param listener the change listener
   */
  public void addChangeListener(ChangeListener listener) {
    listenerList.add(ChangeListener.class, listener);
  }

  /**
   * Removes change listener.
   *
   * @param listener the change listener
   */
  public void removeChangeListener(ChangeListener listener) {
    listenerList.remove(ChangeListener.class, listener);
  }

  /**
   * Returns change listeners.
   *
   * @return change listeners
   */
  public ChangeListener[] getChangeListeners() {
    int count = listenerList.getListenerCount(ChangeListener.class);
    ChangeListener[] listeners = new ChangeListener[count];
    if (count > 0) {
      System.arraycopy(listenerList.getListenersArray(ChangeListener.class),
                       0, listeners,  0, count);
    }
    return listeners;
  }

// Protected Methods. ----------------------------------------------------------

  /**
   * Fires state changed event.
   */
  protected void fireStateChanged() {
    int count = listenerList.getListenerCount(ChangeListener.class);
    if (count > 0) {
      ChangeListener[] listeners = (ChangeListener[])listenerList.
                                       getListenersArray(ChangeListener.class);
      ChangeEvent changeEvent = this.changeEvent;
      if (changeEvent == null) {
        changeEvent = new ChangeEvent(this);
        this.changeEvent = changeEvent;
      }
      for (int i = count; --i >= 0;) {
        listeners[i].stateChanged(changeEvent);
      }
    }
  }

// Overridden Methods. ---------------------------------------------------------

  /**
   * {@inheritDoc}
   */
  protected String paramString() {
    StringBuffer sb = new StringBuffer(80);
    sb.append(super.paramString());
    sb.append(",orientation=");
    if (orientation == HORIZONTAL) {
      sb.append("HORIZONTAL");
    } else {
      sb.append("VERTICAL");
    }
    sb.append(",unitIncrement=");
    sb.append(unitIncrement);
    sb.append(",blockIncrement=");
    sb.append(blockIncrement);
    return sb.toString();
  }

  void componentDestroyed() {
    super.componentDestroyed();
    model.removeChangeListener(changeListener);
  }

// Package-Private Methods. ----------------------------------------------------

  void processChangeEvent() {
    if (!blockModelEvents) {
      final BoundedRangeModel model = this.model;
      final int minimum = model.getMinimum();
      if (this.minimum != minimum) {
        _setMinimum(_delegate, minimum);
        this.minimum = minimum;
      }
      final int maximum = model.getMaximum();
      if (this.maximum != maximum) {
        _setMaximum(_delegate, maximum);
        this.maximum = maximum;
      }
      final int value = model.getValue();
      if (this.value != value) {
        _setValue(_delegate, value);
        this.value = value;
      }
    }
    fireStateChanged();
  }

// Native Callbacks. -----------------------------------------------------------

  void _valueChanged(int value) {
    blockModelEvents = true;
    model.setValue(value);
    blockModelEvents = false;
  }

  void _valueIsAdjusting(boolean adjusting) {
    blockModelEvents = true;
    model.setValueIsAdjusting(adjusting);
    blockModelEvents = false;
  }

// Package-Private Native Methods. ---------------------------------------------

  native long _create();

  native void _setModel(long delegate, int minimum, int maximum, int value);

  native void _setValue(long delegate, int value);

  native void _setMinimum(long delegate, int minimum);

  native void _setMaximum(long delegate, int maximum);

  native void _setOrientation(long delegate, int orientation);

  native void _setUnitIncrement(long delegate, int unitIncrement);

  native void _setBlockIncrement(long delegate, int blockIncrement);

// Private Static Native Methods. ----------------------------------------------

  private static native void _initialize();

// Inner Classes. --------------------------------------------------------------

  /**
   * Model Change Listener.
   */
  static class ModelListener implements ChangeListener {
    final ScrollBar scrollBar;
    public ModelListener(ScrollBar scrollBar) {
      this.scrollBar = scrollBar;
    }
    public void stateChanged(ChangeEvent e) {
      scrollBar.processChangeEvent();
    }
  }

}
